package psd.model.Prolog;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration;
import org.eclipse.jdt.core.dom.Comment;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.DoStatement;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Initializer;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.WhileStatement;

import psd.model.Prolog.Converters.CommentHelper;
import psd.model.Prolog.Converters.PrologCommon;


public class PrologConverter {

	protected FactGenerationToolbox generationToolBox;
	protected IIDResolver idResolver;
	protected IFactWriter writer;
	protected ITypeResolver typeResolver;
	protected Hashtable labels;
	protected ICompilationUnit iCompilationUnit;
	protected IBuffer buffer;
	protected Hashtable packages;
	protected List CALLT_THAT_NEED_IDENT;
	protected Map relinkedParents;
	protected CommentHelper commentHelper;
	HashMap commentMap;
	HashMap commentTableRight;
	HashMap commentTableLeft;
	protected Hashtable annotations;

	protected PrologConverter(FactGenerationToolbox toolbox) {
		generationToolBox = toolbox;
		idResolver = generationToolBox.getIDResolver();
		writer = generationToolBox.getFactWriter();
		typeResolver = generationToolBox.getTypeResolver();
		labels = generationToolBox.getLabels();
		packages = generationToolBox.getPackages();
		CALLT_THAT_NEED_IDENT = new ArrayList();
		relinkedParents = new Hashtable();
		iCompilationUnit = generationToolBox.getIcompilationUnit();
		commentTableRight = new HashMap();
		commentTableLeft = new HashMap();
		commentHelper = new CommentHelper(iCompilationUnit);
	}
	
	protected PrologConverter(){
		
	}

	protected String getParentId(ASTNode obj) {
		String parent = (String) generationToolBox.getRelinkedParents()
				.get(obj);
		if (parent == null) {
			return idResolver.getID(obj.getParent());
		} else {
			return parent;
		}
	}
	
	protected String getLineNumber(ASTNode obj)
	{
		CompilationUnit unit = (CompilationUnit)obj.getRoot();
        Integer lineNumber = unit.getLineNumber(obj.getStartPosition());
        String line = lineNumber.toString();
        return line;
	}
	
	protected String getFilePath(ASTNode obj)
	{
		CompilationUnit unit = (CompilationUnit)obj.getRoot();
		String filePath = PrologCommon.quote(unit.getJavaElement().getPath().makeAbsolute().toString());
		return filePath;
	}

	protected void createBodyFact(ASTNode node, String name, String args[]) {
		String toPass[] = new String[args.length + 3];
		toPass[0] = idResolver.getID(node);
		toPass[1] = getParentId(node);
		toPass[2] = idResolver.getID(getEnclosingNode(node));
		System.arraycopy(args, 0, toPass, 3, args.length);
		writer.writeFact(name, toPass);
		writer.writeFact("slT", new String[] { toPass[0],
				Integer.toString(node.getStartPosition()),
				Integer.toString(node.getLength()) });

	}

	protected ASTNode getEnclosingNode(ASTNode a) {
		if (a == null) {
			throw new RuntimeException("enclosing node is null");
		}
		if ((a instanceof MethodDeclaration)
				|| (a instanceof VariableDeclarationFragment)
				&& (a.getParent() instanceof FieldDeclaration)
				|| (a instanceof FieldDeclaration)
				|| (a instanceof AbstractTypeDeclaration)
				|| (a instanceof AnnotationTypeMemberDeclaration)
				|| (a instanceof Initializer)) {
			return a;
		} else {
			return getEnclosingNode(a.getParent());
		}
	}

	protected void writeSourceLocationArgumentRaw(ASTNode node,
			String argument, int start, int length) {
		writer.writeFact("sl_argT", new String[] { idResolver.getID(node),
				argument, Integer.toString(start), Integer.toString(length), this.getFilePath(node), this.getLineNumber(node) });
	}

	protected ASTNode getEnclosingIterator(ASTNode node) {
		for (; !(node instanceof ForStatement)
				&& !(node instanceof EnhancedForStatement)
				&& !(node instanceof DoStatement)
				&& !(node instanceof SwitchStatement)
				&& !(node instanceof WhileStatement); node = node.getParent()) {
			if (node == null) {
				throw new IllegalArgumentException(
						"node should _NOT_ be null, huh?");
			}
		}

		return node;
	}

	protected String quote(String str) {
		return (new StringBuilder("'")).append(str).append("'").toString();
	}

	protected String quote(SimpleName name) {
		return quote(name.toString());
	}

	protected void writeSourceLocationArgumentIdentifier(ASTNode node, Name name) {
		generationToolBox.getFactWriter().writeFact(
				"sl_argT",
				new String[] {
						generationToolBox.getIDResolver().getID(node),
						(new StringBuilder("identifier(")).append(
								quote(name.toString())).append(")").toString(),
						Integer.toString(name.getStartPosition()),
						Integer.toString(name.getLength()), this.getFilePath(node), this.getLineNumber(node) });
	}

	protected void writeSourceLocationArgumentIdentifier(ASTNode node,
			Name name, List list) {
		writeSourceLocationArgumentIdentifier(node, name);
		for (Iterator iter = list.iterator(); iter.hasNext();) {
			Object element = iter.next();
			if (element instanceof Modifier) {
				Modifier modifier = (Modifier) element;
				generationToolBox.getFactWriter().writeFact(
						"sl_argT",
						new String[] {
								generationToolBox.getIDResolver().getID(node),
								(new StringBuilder("modifier(")).append(
										modifier.getKeyword().toString())
										.append(")").toString(),
								Integer.toString(modifier.getStartPosition()),
								Integer.toString(modifier.getLength()), this.getFilePath(node), this.getLineNumber(node) });
			}
		}

	}

	protected void writeSourceLocationArgument(ASTNode node, String kind) {
		writer.writeFact("sl_argT", new String[] { idResolver.getID(node),
				kind, Integer.toString(node.getStartPosition()),
				Integer.toString(node.getLength()) , this.getFilePath(node), this.getLineNumber(node)});
	}

	protected String getTextForNode(ASTNode node) {
		return getTextForNode(node, 0, 0);
	}

	protected String getTextForNode(ASTNode node, int offsetdiff, int lendiff) {
		int startPos = node.getStartPosition();
		int length = node.getLength();
		if (length <= 0) {
			return null;
		}
		try {
			if (buffer == null) {
				buffer = iCompilationUnit.getBuffer();
			}
		} catch (JavaModelException e) {
			// JTDebug.report(e);
		}
		return buffer.getText(startPos + offsetdiff, (length + lendiff)
				- offsetdiff);
	}

	protected void generatePackageFactIfNecessary(IBinding binding) {
		if (binding == null) {
			// JTDebug.warning("generatePackageFactIfNecessary called on null binding");
			return;
		}
		String fullName = binding.getName();
		if (binding.getKind() == 1 && packages.get(fullName) == null) {
			String id = idResolver.getID(binding);
			writer.writeFact("packageT", new String[] { id, quote(fullName) });
			packages.put(fullName, fullName);
		}
	}

	protected void writeModifiers(ASTNode node, int modifiers) {
		if (Modifier.isPublic(modifiers)) {
			writeModifier(node, "public");
		}
		if (Modifier.isPrivate(modifiers)) {
			writeModifier(node, "private");
		}
		if (Modifier.isStatic(modifiers)) {
			writeModifier(node, "static");
		}
		if (Modifier.isAbstract(modifiers)) {
			writeModifier(node, "abstract");
		}
		if (Modifier.isFinal(modifiers)) {
			writeModifier(node, "final");
		}
		if (Modifier.isProtected(modifiers)) {
			writeModifier(node, "protected");
		}
		if (Modifier.isNative(modifiers)) {
			writeModifier(node, "native");
		}
		if (Modifier.isSynchronized(modifiers)) {
			writeModifier(node, "synchronized");
		}
		if (Modifier.isTransient(modifiers)) {
			writeModifier(node, "transient");
		}
		if (Modifier.isVolatile(modifiers)) {
			writeModifier(node, "volatile");
		}
		if (Modifier.isStrictfp(modifiers)) {
			writeModifier(node, "strictfp");
		}
	}

	protected void writeModifier(ASTNode node, String modifier) {
		generationToolBox.getFactWriter().writeFact(
				"modifierT",
				new String[] {
						generationToolBox.getIDResolver().getID(node),
						(new StringBuilder("'")).append(modifier).append("'")
								.toString(), this.getFilePath(node), this.getLineNumber(node) });
	}

	protected void handleSelectsIdents(Name name, String selectedFrom) {
		IBinding binding = name.resolveBinding();
		generatePackageFactIfNecessary(binding);
		if (selectedFrom == null) {
			selectedFrom = idResolver.getID(name.getParent());
		}
		if (name instanceof SimpleName) {
			if (binding instanceof ITypeBinding) {
				String args[] = { idResolver.getID(name), selectedFrom,
						idResolver.getID(getEnclosingNode(name)),
						idResolver.getID((ITypeBinding) binding) };
				writer.writeFact("typeRefT", args);
			} else if (binding instanceof IVariableBinding) {
				String args[] = { idResolver.getID(name), selectedFrom,
						idResolver.getID(getEnclosingNode(name)),
						quote((SimpleName) name), idResolver.getID(binding) };
				writer.writeFact("identT", args);
			} else {
				String msg = (new StringBuilder(
						"Unexpected binding for (qualified) name: ")).append(
						binding.getClass().getName()).toString();
				// JTDebug.error(msg);
				throw new RuntimeException(msg);
			}
			writer.writeFact("slT", new String[] { idResolver.getID(name),
					Integer.toString(name.getStartPosition()),
					Integer.toString(name.getLength()) });
			writeSourceLocationArgumentIdentifier(name, name);
		} else {
			String args[] = { idResolver.getID(name), selectedFrom,
					idResolver.getID(getEnclosingNode(name)),
					idResolver.getID((ITypeBinding) binding) };
			writer.writeFact("typeRefT", args);
			writer.writeFact("slT", new String[] { idResolver.getID(name),
					Integer.toString(name.getStartPosition()),
					Integer.toString(name.getLength()) });
			writeSourceLocationArgumentIdentifier(name, name);
		}
	}

	protected TypeDeclaration getUltimateAncestor(ASTNode node) {
		for (; !(node instanceof TypeDeclaration); node = node.getParent()) {
		}
		return (TypeDeclaration) node;
	}

	protected void checkAndGenComments(ASTNode node, String id) {
		 int offsetAndLength[] = commentHelper.excludeComments(node);
		 List commentList = new ArrayList();
		 int nodeStart = offsetAndLength[0];
		 int nodeOffset = offsetAndLength[1];
		 List lineCommentsLeft = new ArrayList();
		 List lineCommentsRight = new ArrayList();
		 if((commentTableRight == null) | (commentTableRight == null))
		 {
		 initComments((CompilationUnit)node.getRoot());
		 }
		 if(commentTableRight.get(Integer.valueOf(nodeStart)) != null)
		 {
		 List temp = (List)commentTableRight.get(Integer.valueOf(nodeStart));
		 for(Iterator iterator1 = temp.iterator(); iterator1.hasNext();)
		 {
		 Comment com = (Comment)iterator1.next();
		 if(com.isLineComment())
		 {
		 lineCommentsRight.add(com);
		 }
		 if(com.isBlockComment())
		 {
		 commentList.add(com);
		 }
		 }
		
		 commentTableRight.remove(Integer.valueOf(nodeStart));
		 }
		 if(commentTableLeft.get(Integer.valueOf(nodeStart + nodeOffset)) !=
		 null)
		 {
		 List temp = (List)commentTableLeft.get(Integer.valueOf(nodeStart +
		 nodeOffset));
		 for(Iterator iterator2 = temp.iterator(); iterator2.hasNext();)
		 {
		 Comment com = (Comment)iterator2.next();
		 if(com.isLineComment())
		 {
		 lineCommentsLeft.add(com);
		 }
		 if(com.isBlockComment())
		 {
		 commentList.add(com);
		 }
		 }
		
		 commentTableLeft.remove(Integer.valueOf(nodeStart + nodeOffset));
		 }
		 Comment comment;
		 for(Iterator iterator = commentList.iterator(); iterator.hasNext();
		 writeCommentFact(id, comment))
		 {
		 comment = (Comment)iterator.next();
		 }
		
		 if(!lineCommentsLeft.isEmpty())
		 {
		 Integer start = commentHelper.minimumStart(lineCommentsLeft);
		 Integer end = commentHelper.maximumEnd(lineCommentsLeft);
		 Integer offset = Integer.valueOf(end.intValue() - start.intValue());
		 writeLineComments(id, start, offset);
		 }
		 if(!lineCommentsRight.isEmpty())
		 {
		 Integer start = commentHelper.minimumStart(lineCommentsRight);
		 Integer end = commentHelper.maximumEnd(lineCommentsRight);
		 Integer offset = Integer.valueOf(end.intValue() - start.intValue());
		 writeLineComments(id, start, offset);
		 }
	}

	private void initComments(CompilationUnit node) {
		List blockOrlineCommentList = new ArrayList();
		for (Iterator iter = node.getCommentList().iterator(); iter.hasNext();) {
			Comment comment = (Comment) iter.next();
			if (!comment.isDocComment()) {
				blockOrlineCommentList.add(comment);
				List starterListRight = new ArrayList(1);
				List starterListLeft = new ArrayList(1);
				starterListRight.add(comment);
				int distanceToTheRight = commentHelper
						.getDistanceToTheRight(comment);
				commentTableRight.put(Integer.valueOf(distanceToTheRight),
						starterListRight);
				starterListLeft.add(comment);
				int distanceToTheLeft = commentHelper
						.getDistanceToTheLeft(comment);
				commentTableLeft.put(Integer.valueOf(distanceToTheLeft),
						starterListLeft);
				commentHelper.addEquivalentKeys(distanceToTheLeft,
						distanceToTheRight);
			}
		}

		commentHelper.resolvedTransitiveRelationsLeft(commentTableLeft);
		commentHelper.resolvedTransitiveRelationsRight(commentTableRight);
		commentHelper.decideOwner(commentTableLeft, commentTableRight);
	}
	
	protected void writeCommentFact(String id, Comment comment)
    {
        String commentid = idResolver.getID();
        String commentOwnerId = id;
        String commentType;
        if(comment.isBlockComment())
        {
            commentType = "blockComment";
        } else
        {
            commentType = "lineComment";
        }
        String cmtargs[] = {
            commentid, commentOwnerId, commentType
        };
        writer.writeFact("commentT", cmtargs);
        writer.writeFact("slT", new String[] {
            commentid, Integer.toString(comment.getStartPosition()), Integer.toString(comment.getLength())
        });
    }

    protected void writeLineComments(String id, Integer offset, Integer length)
    {
        String commentid = idResolver.getID();
        String commentOwnerId = id;
        String cmtargs[] = {
            commentid, commentOwnerId, "lineComment"
        };
        writer.writeFact("commentT", cmtargs);
        writer.writeFact("slT", new String[] {
            commentid, Integer.toString(offset.intValue()), Integer.toString(length.intValue())
        });
    }


	protected void visitAbstractTypeDeclaration(AbstractTypeDeclaration node,
			String defaultConstructorId, List additionalMembers) {
		String parentId;
		if (node.getParent().getNodeType() == 15) {
			parentId = idResolver.getID((CompilationUnit) node.getParent());
		} else {
			parentId = getParentId(node);
		}
		Iterator bodyIterator = node.bodyDeclarations().iterator();
		ArrayList expandedList = PrologCommon.expandList(bodyIterator);
		String prologList = idResolver.getIDs(expandedList);
		if (defaultConstructorId != null) {
			prologList = prologList.substring(1);
			String prefix = (new StringBuilder("[")).append(
					defaultConstructorId).toString();
			if (prologList.equals("]")) {
				prologList = (new StringBuilder(String.valueOf(prefix)))
						.append(prologList).toString();
			} else {
				prologList = (new StringBuilder(String.valueOf(prefix)))
						.append(", ").append(prologList).toString();
			}
		}
		if (additionalMembers != null && additionalMembers.size() > 1) {
			String additionalMemberList = idResolver.getIDs(additionalMembers);
			prologList = (new StringBuilder(String.valueOf(additionalMemberList
					.substring(0, additionalMemberList.length() - 1)))).append(
					prologList.length() <= 2 ? "" : ", ").append(
					prologList.substring(1)).toString();
		}
		String id = idResolver.getID(node);
		String args[] = { id, parentId, quote(node.getName()), prologList, this.getFilePath(node), this.getLineNumber(node) };
		//writer.addIndention();
		writer.writeFact("classDefT", args);
		// createAnnotationFact(node, id);
		 int offsetAndLength[] = commentHelper.excludeComments(node);
		 writer.writeFact("slT", new String[] {
		 idResolver.getID(node), Integer.toString(offsetAndLength[0]),
		 Integer.toString(offsetAndLength[1])
		 });
		writeModifiers(node, node.getModifiers());
		checkAndGenComments(node, id);
		writeSourceLocationArgumentIdentifier(node, node.getName(), node
				.modifiers());
	}

	protected void visitAnnotation(Annotation node, String values) {
		String id = idResolver.getID(node);
		writer.writeFact("annotatedT", new String[] { getParentId(node), id, this.getFilePath(node), this.getLineNumber(node) });
		checkAndGenComments(node, id);
		writer.writeFact("annotationT", new String[] {
				id,
				getParentId(node),
				idResolver.getID(getEnclosingNode(node)),
				idResolver.getID(node.resolveAnnotationBinding()
						.getAnnotationType()), values, this.getFilePath(node), this.getLineNumber(node)});
		checkAndGenComments(node, getParentId(node));
	}

}
